子流程(Sub-process)是一个包含其他节点,网关,事件等等的节点。 它自己就是一个流程,同时是更大流程的一部分。 子流程是完全定义在父流程里的 (这就是为什么叫做内嵌子流程)。
子流程有两种主要场景:
使用子流程要考虑如下限制:
子流程显示为标准的节点,圆角矩形。 这时子流程是折叠的,只显示名称和一个加号标记,展示了高级别的流程总览:
这时子流程是展开的,子流程的步骤都显示在子流程边界内:
使用子流程的主要原因,是定义对应事件的作用域。下面流程模型演示了这个功能:调查软件/调查引荐任务需要同步执行,两个任务需要在同时完成,在二线支持解决之前。 这里,定时器的作用域(比如,节点需要及时完成)是由子流程限制的。
子流程定义为 subprocess 元素。 所有节点,网关,事件,等等。它是子流程的一部分,需要放在这个元素里。
<subProcess id="subProcess">
<startEvent id="subProcessStart" />
... other Sub-Process elements ...
<endEvent id="subProcessEnd" />
</subProcess>
事件子流程是 BPMN 2.0 中的新元素。事件子流程是由事件触发的子流程。 事件子流程可以添加到流程级别或任意子流程级别。用于触发事件子流程的事件是使用开始事件配置的。为此,事件子流程是不支持空开始事件的。 事件子流程可以被消息事件,错误事件,信号事件,定时器事件,或补偿事件触发。开始事件的订阅在包含事件子流程的作用域(流程实例 或子流程)创建时就会创建。 当作用域销毁也会删除订阅。
事件子流程可以是中断的或非中断的。一个中断的子流程会取消当前作用域内的所有流程。非中断事件子流程会创建那一个新的同步分支。中断事件子流程只会被每个激活状态的宿主触发一次, 非中断事件子流程可以触发多次。子流程是否是终端的,配置使用事件子流程的开始事件配置。
事件子流程不能有任何进入和外出流程。当事件触发一个事件子流程时,输入顺序流是没有意义的。 当事件子流程结束时,无论当前作用域已经结束了(中断事件子流程的情况),或为非中断子流程生成同步分支会结束。
当前的限制:
事件子流程可以显示为边框为虚线的内嵌子流程。
事件子流程的 XML 内容与内嵌子流程是一样的。 另外,要把triggeredByEvent 属性设置为 true:
<subProcess id="eventSubProcess" triggeredByEvent="true">
...
</subProcess>
下面是一个使用错误开始事件触发的事件子流程的实例。事件子流程是放在“流程级别”的,意思是,作用于流程实例:
事件子流程的XML如下所示:
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition errorRef="error" />
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Provide additional data" />
</subProcess>
如上面所述,事件子流程也可以添加成内嵌子流程。如果添加为内嵌子流程,它其实是边界事件的一种替代方案。 考虑下面两个流程图。两种情况内嵌子流程会抛出一个错误事件。两种情况错误都会被捕获并使用一个用户任务处理。
相对于:
两种场景都会执行相同的任务。然而,两种建模的方式是不同的:
这两个不同点可以帮助我们决定是使用边界事件还是内嵌事件子流程来解决特定的流程建模/实现问题。
[试验]
事务子流程是内嵌子流程,可以用来把多个流程放到一个事务里。 事务是一个逻辑单元,可以把一些单独的节点放在一起,这样它们就可以一起成功或一起失败。事务可能的结果: 事务可以有三种可能的结果:
下面的图形演示了三种不同的结果:
与A CID 事务的关系:一定不要把 bpmn 事务子流程与技术(ACID)事务相混淆。 bpmn事务子流程不是技术事务领域的东西。要理解 activiti 中的事务管理,请参考 并发与事务。 bpmn 事务和技术事务有以下不同点:
因为 bpmn 事务会跨越多个 ACID 事务,所以会丧失 ACID 的特性。比如,考虑上述例子。 假设“约定旅店”和“刷信用卡”操作在单独的ACID事务中执行。 也假设“预定旅店”节点已经成功了。现在我们处于一个中间不稳定状态,因为我们预定了酒店,但是还没有刷信用卡。 现在,在一个ACID事务中,我们要依次执行不同的操作,也会有一个中间不稳定状态。 不同的是,这个中间状态对事务的外部是可见的。 比如,如果通过外部预定服务进行了预定,其他使用相同预定服务的部分就可以看到旅店被预定了。 这意味着实现业务事务时,我们完全失去了隔离属性 (注:我们也经常放弃隔离性,来为ACID事务获得更高的并发,但是我们可以完全控制,中间不稳定状态也只持续很短的时间)。
bpmn 业务事务也不能使用通常的方式回滚。因为它跨越了多个事务,bpmn 事务取消时一些 ACID 事务可能已经提交了。 这时,它们不能被回滚了。
因为 bpmn 事务实际上运行时间很长,缺乏隔离性和回滚机制都需要被区别对待。 实际上,这里也没有更好的办法在特定领域处理这些问题:
综上所述:ACID 处理的是通常问题(回滚,隔离级别和启发式结果), 在实现业务事务时,我们需要找到特定领域的解决方案来处理这些问题。
目前的限制:
ACID事务顶层的一致性和优化并发: bpmn 事务保证一致性,要么所有节点都成功,或者一些节点成功,对其他成功的节点进行补偿。 无论哪种方式,都会有一致性的结果。不过要讨论一些 activiti 内部的情况,bpmn 事务的一致性模型是叠加在流程的一致性模型之上的。activiti 执行流程是事务性的。并发使用了乐观锁。在 activiti 中,bpmn 错误,取消和补偿事件都建立在同样的 acid 事务与乐观锁之上。 比如,取消结束事件只能触发它实际到达的补偿。如果之前服务任务抛出了未声明的异常。或者, 补偿处理器的效果无法提交,如果底层的 acid 事务的参与者把事务设置成必须回滚。 或者当两个并发流程到达了取消结束事件,可 能会触发两次补偿,并因为乐观锁异常失败。 所有这些都说明 activiti 中实现 bpmn 事务时,相同的规则也作用域普通的流程和子流程。 所以为了保证一致性,重要的是使用一种方式考虑实现乐观事务性的执行模型。
事务子流程显示为内嵌子流程,使用双线边框。
事务子流程使用 transaction 标签:
<transaction id="myTransaction" >
...
</transaction>
下面是事务子流程的实例:
BPMN 2.0 区分了普通子流程, 也叫做内嵌子流程,和调用节点,看起来很相似。 从概念上讲,当流程抵达时,两者都会调用子流程。
不同点是调用节点引用流程定义外部的一个流程,子流程 会内嵌到原始的流程定义中。使用调用节点的主要场景是需要重用流程定义, 这个流程定义需要被很多其他流程定义调用的时候。
当流程执行到调用节点,会创建一个新分支,它是到达调用节点的流程的分支。 这个分支会用来执行子流程,默认创建并行子流程,就像一个普通的流程。 上级流程会等待子流程完成,然后才会继续向下执行
调用节点显示与子流程相同, 不过是粗边框(无论是折叠和展开的)。 根据不同的建模工具,调用节点也可以展开,但是显示为折叠的子流程。
有种调用节点是经常性的节点,需要 calledElement 引用被 key 定义的流程。在实践中,这意味着流程的 ID 用于 calledElement。
<callActivity id="callCheckCreditProcess" name="Check credit" calledElement="checkCreditProcess" />
注意,子流程的流程定义是在执行阶段解析的。 就是说子流程可以与调用的流程分开部署,如果需要的话。
可以把流程变量传递给子流程,反之亦然。数据会复制给子流程,当它启动的时候, 并在它结束的时候复制回主流程。
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<activiti:in source="someVariableInMainProcess" target="nameOfVariableInSubProcess" />
<activiti:out source="someVariableInSubProcss" target="nameOfVariableInMainProcess" />
</extensionElements>
</callActivity>
我们使用 Activiti 扩展来简化 BPMN 标准元素调用dataInputAssociation 和 dataOutputAssociation,这只在你使用 BPMN 2.0 标准方式声明流程变量才管用。
这里也可以使用表达式:
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<activiti:in sourceExpression="${x+5}"" target="y" />
<activiti:out source="${y+5}" target="z" />
</extensionElements>
</callActivity>
最后 z = y + 5 = x + 5 + 5
下面的流程图演示了简单订单处理。先判断客户端信用,这可能与很多其他流程相同。 检查信用阶段这里设计成调用节点。
流程看起来像这样的:
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="receiveOrder" />
<manualTask id="receiveOrder" name="Receive Order" />
<sequenceFlow id="flow2" sourceRef="receiveOrder" targetRef="callCheckCreditProcess" />
<callActivity id="callCheckCreditProcess" name="Check credit" calledElement="checkCreditProcess" />
<sequenceFlow id="flow3" sourceRef="callCheckCreditProcess" targetRef="prepareAndShipTask" />
<userTask id="prepareAndShipTask" name="Prepare and Ship" />
<sequenceFlow id="flow4" sourceRef="prepareAndShipTask" targetRef="end" />
<endEvent id="end" />
子流程看起来像这样的: